use std::{borrow::Cow, task::Poll};

use actix_web::{body::{BoxBody, MessageBody}, http::header::ContentType, web::Bytes, HttpRequest, HttpResponse, Responder, ResponseError};
use serde::Serialize;

use crate::error::{AppErr, ErrInfo};

use super::utils::{Cbor, Void};

const HEAD_RESP_TYPE: &'static str = "resp";
const RESP_TYPE_OK: &'static str = "ok";
const RESP_TYPE_FAIL: &'static str = "fail";


impl <T: Serialize> Responder for Cbor<T> {

    type Body = BoxBody;

    fn respond_to(self, _req: &HttpRequest) -> HttpResponse {
        let body = serde_cbor::to_vec(&self.0).unwrap();
        HttpResponse::Ok()
        .content_type(ContentType::octet_stream())
        .insert_header((HEAD_RESP_TYPE, RESP_TYPE_OK))
        .body(body)
    }
}

impl Responder for Void {

    type Body = BoxBody;

    fn respond_to(self, _req: &HttpRequest) -> HttpResponse {
        HttpResponse::Ok()
        .content_type(ContentType::octet_stream())
        .insert_header((HEAD_RESP_TYPE, RESP_TYPE_OK))
        .finish()
    }
}

impl ResponseError for AppErr {

    fn error_response(&self) -> HttpResponse {
        
        let body = match self {
            AppErr::Service(info) => {
                serde_cbor::to_vec(info).unwrap()
            },
            _ => {
                let info = ErrInfo {
                    err_code: -1,
                    err_msg: Cow::Owned(self.to_string())
                };
                serde_cbor::to_vec(&info).unwrap()
            }
        };
        HttpResponse::Ok()
        .content_type(ContentType::octet_stream())
        .insert_header((HEAD_RESP_TYPE, RESP_TYPE_FAIL))
        .body(body)
    }   
}

pub struct Download {
    name: String,
    body: Box<[u8]>,
}

impl Download {

    pub fn new(name: String, body: Box<[u8]>) -> Self {
        Self { name, body }
    }
}

pub struct ByteBody(Box<[u8]>);

impl MessageBody for ByteBody {
    type Error = AppErr;

    fn poll_next(
            self: std::pin::Pin<&mut Self>,
            _cx: &mut std::task::Context<'_>,
        ) -> Poll<Option<Result<Bytes, Self::Error>>> {
        
        let this = self.get_mut();
        if this.0.is_empty() {
            return Poll::Ready(None);
        }
        let body = std::mem::take(&mut this.0);
        Poll::Ready(Some(Ok(Bytes::from(body))))
    }

    fn size(&self) -> actix_web::body::BodySize {
        actix_web::body::BodySize::Sized(self.0.len() as u64)
    }

    fn try_into_bytes(self) -> Result<Bytes, Self>
        where
            Self: Sized, {
        Ok(Bytes::from(self.0))
    }
}

impl Responder for Download {

    type Body = BoxBody;

    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
        let name = format!(r#"attachment; filename="{}""#, self.name);
        HttpResponse::Ok()
            .content_type(ContentType::octet_stream())
            .insert_header((HEAD_RESP_TYPE, RESP_TYPE_OK))
            .insert_header(("Content-Disposition", name))
            .body(ByteBody(self.body))
    }
}
